/*-------------------------------------------------------------------------
 *
 * dropcmds.c
 *      handle various "DROP" operations
 *
 * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * This source code file contains modifications made by THL A29 Limited ("Tencent Modifications").
 * All Tencent Modifications are Copyright (C) 2023 THL A29 Limited.
 *
 * IDENTIFICATION
 *      src/backend/catalog/dropcmds.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/heapam.h"
#include "access/htup_details.h"
#include "catalog/dependency.h"
#include "catalog/namespace.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_class.h"
#include "catalog/pg_proc.h"
#include "commands/defrem.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "parser/parse_type.h"
#include "utils/builtins.h"
#include "utils/syscache.h"
#ifdef __OPENTENBASE__
#include "utils/rel.h"
#include "catalog/catalog.h"
#include "storage/lmgr.h"
#endif
#ifdef _MLS_
#include "utils/mls.h"
#endif

static void does_not_exist_skipping(ObjectType objtype,
						Node *object, bool missing_ok);
static bool owningrel_does_not_exist_skipping(List *object,
                                  const char **msg, char **name);
static bool schema_does_not_exist_skipping(List *object,
                               const char **msg, char **name);
static bool type_in_list_does_not_exist_skipping(List *typenames,
                                     const char **msg, char **name);

/*
 * Check object exists or not before remove.
 */
ObjectAddresses* PreCheckforRemoveObjects(DropStmt *stmt, bool missing_ok,
										bool *need_drop, char *query_string,
										bool need_unlock)
{
    ObjectAddresses *objects;
    ListCell   *cell1;
	bool        querystring_omit = false;

    objects = new_object_addresses();
	*need_drop = false;

    foreach(cell1, stmt->objects)
    {
        ObjectAddress address;
        Node       *object = lfirst(cell1);
        Relation    relation = NULL;
        Oid            namespaceId;

        /* Get an ObjectAddress for the object. */
        address = get_object_address(stmt->removeType,
                                     object,
                                     &relation,
                                     AccessExclusiveLock,
                                     stmt->missing_ok);

        /*
         * Issue NOTICE if supplied object was not found.  Note this is only
         * relevant in the missing_ok case, because otherwise
         * get_object_address would have thrown an error.
         */
        if (!OidIsValid(address.objectId))
        {
			char *relation_name = NULL;
            Assert(stmt->missing_ok);

			does_not_exist_skipping(stmt->removeType, object, missing_ok);

#ifdef __OPENTENBASE__
			if (query_string)
			{
				if (!querystring_omit)
				{
					OmitqueryStringSpace(query_string);
					querystring_omit = true;
				}
				relation_name = GetRemoveObjectName(stmt->removeType, object);
				RemoveObjnameInQueryString(query_string, relation_name);
			}
#endif
            continue;
        }

#ifdef __OPENTENBASE__
        /* could not drop trigger on child partition */
        if (OBJECT_TRIGGER == stmt->removeType && relation)
        {
            if (RELATION_IS_CHILD(relation))
            {
                elog(ERROR, "drop trigger on child interval partition is not permitted");
            }
        }
#endif

        /*
         * Although COMMENT ON FUNCTION, SECURITY LABEL ON FUNCTION, etc. are
         * happy to operate on an aggregate as on any other function, we have
         * historically not allowed this for DROP FUNCTION.
         */
        if (stmt->removeType == OBJECT_FUNCTION)
        {
            Oid            funcOid = address.objectId;
            HeapTuple    tup;

            tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcOid));
            if (!HeapTupleIsValid(tup)) /* should not happen */
                elog(ERROR, "cache lookup failed for function %u", funcOid);

            if (((Form_pg_proc) GETSTRUCT(tup))->proisagg)
                ereport(ERROR,
                        (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                         errmsg("\"%s\" is an aggregate function",
                                NameListToString(castNode(ObjectWithArgs, object)->objname)),
                         errhint("Use DROP AGGREGATE to drop aggregate functions.")));

            ReleaseSysCache(tup);
        }

        /* Check permissions. */
        namespaceId = get_object_namespace(&address);
        if (!OidIsValid(namespaceId) ||
            !pg_namespace_ownercheck(namespaceId, GetUserId()))
            check_object_ownership(GetUserId(), stmt->removeType, address,
                                   object, relation);

        /* Release any relcache reference count, but keep lock until commit. */
        if (relation)
            heap_close(relation, NoLock);

        add_exact_object_address(&address, objects);
#ifdef _MLS_
        if (OBJECT_SCHEMA == stmt->removeType)
        {
            if(true == mls_check_schema_permission(address.objectId))
            {
#ifdef _PG_REGRESS_                
                elog(ERROR, "could not drop schema, cause there are tables having crypt poilcy bound in this schema or schema has crypt bound");
#else
                elog(ERROR, "could not drop schema oid:%d, cause there are tables having crypt poilcy bound in this schema or schema has crypt bound", 
                        address.objectId);
#endif
            }
        }
#endif
		*need_drop = true;
		if (need_unlock)
		{
			if (IsSharedRelation(address.classId))
            	UnlockSharedObject(address.classId, address.objectId, 0, AccessExclusiveLock);
			else
				UnlockDatabaseObject(address.classId, address.objectId, 0, AccessExclusiveLock);
		}
	}
	return objects;
}

/*
 * Drop one or more objects.
 *
 * We don't currently handle all object types here.  Relations, for example,
 * require special handling, because (for example) indexes have additional
 * locking requirements.
 *
 * We look up all the objects first, and then delete them in a single
 * performMultipleDeletions() call.  This avoids unnecessary DROP RESTRICT
 * errors if there are dependencies between them.
 */
void
RemoveObjects(DropStmt *stmt, bool missing_ok, bool *need_drop, char *query_string)
{
	ObjectAddresses *objects;
        
	objects = PreCheckforRemoveObjects(stmt, missing_ok, need_drop,
										query_string, false);

    /* Here we really delete them. */
    performMultipleDeletions(objects, stmt->behavior, 0);

    free_object_addresses(objects);
}

/*
 * owningrel_does_not_exist_skipping
 *        Subroutine for RemoveObjects
 *
 * After determining that a specification for a rule or trigger returns that
 * the specified object does not exist, test whether its owning relation, and
 * its schema, exist or not; if they do, return false --- the trigger or rule
 * itself is missing instead.  If the owning relation or its schema do not
 * exist, fill the error message format string and name, and return true.
 */
static bool
owningrel_does_not_exist_skipping(List *object, const char **msg, char **name)
{
    List       *parent_object;
    RangeVar   *parent_rel;

    parent_object = list_truncate(list_copy(object),
                                  list_length(object) - 1);

    if (schema_does_not_exist_skipping(parent_object, msg, name))
        return true;

    parent_rel = makeRangeVarFromNameList(parent_object);

    if (!OidIsValid(RangeVarGetRelid(parent_rel, NoLock, true)))
    {
        *msg = gettext_noop("relation \"%s\" does not exist, skipping");
        *name = NameListToString(parent_object);

        return true;
    }

    return false;
}

/*
 * schema_does_not_exist_skipping
 *        Subroutine for RemoveObjects
 *
 * After determining that a specification for a schema-qualifiable object
 * refers to an object that does not exist, test whether the specified schema
 * exists or not.  If no schema was specified, or if the schema does exist,
 * return false -- the object itself is missing instead.  If the specified
 * schema does not exist, fill the error message format string and the
 * specified schema name, and return true.
 */
static bool
schema_does_not_exist_skipping(List *object, const char **msg, char **name)
{
    RangeVar   *rel;

    rel = makeRangeVarFromNameList(object);

    if (rel->schemaname != NULL &&
        !OidIsValid(LookupNamespaceNoError(rel->schemaname)))
    {
        *msg = gettext_noop("schema \"%s\" does not exist, skipping");
        *name = rel->schemaname;

        return true;
    }

    return false;
}

/*
 * type_in_list_does_not_exist_skipping
 *        Subroutine for RemoveObjects
 *
 * After determining that a specification for a function, cast, aggregate or
 * operator returns that the specified object does not exist, test whether the
 * involved datatypes, and their schemas, exist or not; if they do, return
 * false --- the original object itself is missing instead.  If the datatypes
 * or schemas do not exist, fill the error message format string and the
 * missing name, and return true.
 *
 * First parameter is a list of TypeNames.
 */
static bool
type_in_list_does_not_exist_skipping(List *typenames, const char **msg,
                                     char **name)
{
    ListCell   *l;

    foreach(l, typenames)
    {
        TypeName   *typeName = lfirst_node(TypeName, l);

        if (typeName != NULL)
        {
            if (!OidIsValid(LookupTypeNameOid(NULL, typeName, true)))
            {
                /* type doesn't exist, try to find why */
                if (schema_does_not_exist_skipping(typeName->names, msg, name))
                    return true;

                *msg = gettext_noop("type \"%s\" does not exist, skipping");
                *name = TypeNameToString(typeName);

                return true;
            }
        }
    }

    return false;
}

/*
 * does_not_exist_skipping
 *        Subroutine for RemoveObjects
 *
 * Generate a NOTICE stating that the named object was not found, and is
 * being skipped.  This is only relevant when "IF EXISTS" is used; otherwise,
 * get_object_address() in RemoveObjects would have thrown an ERROR.
 */
static void
does_not_exist_skipping(ObjectType objtype, Node *object, bool missing_ok)
{
    const char *msg = NULL;
    char       *name = NULL;
    char       *args = NULL;
	char	   *missmsg = "skipping";
	int			elevel = NOTICE;

	if (!missing_ok)
	{
		missmsg = "can not skip in parallel ddl mode";
		elevel = ERROR;
	}

    switch (objtype)
    {
        case OBJECT_ACCESS_METHOD:
			msg = gettext_noop("access method \"%s\" does not exist, %s");
            name = strVal((Value *) object);
            break;
        case OBJECT_TYPE:
        case OBJECT_DOMAIN:
            {
                TypeName   *typ = castNode(TypeName, object);

                if (!schema_does_not_exist_skipping(typ->names, &msg, &name))
                {
					msg = gettext_noop("type \"%s\" does not exist, %s");
                    name = TypeNameToString(typ);
                }
            }
            break;
        case OBJECT_COLLATION:
            if (!schema_does_not_exist_skipping(castNode(List, object), &msg, &name))
            {
				msg = gettext_noop("collation \"%s\" does not exist, %s");
                name = NameListToString(castNode(List, object));
            }
            break;
        case OBJECT_CONVERSION:
            if (!schema_does_not_exist_skipping(castNode(List, object), &msg, &name))
            {
				msg = gettext_noop("conversion \"%s\" does not exist, %s");
                name = NameListToString(castNode(List, object));
            }
            break;
        case OBJECT_SCHEMA:
			msg = gettext_noop("schema \"%s\" does not exist, %s");
            name = strVal((Value *) object);
            break;
        case OBJECT_STATISTIC_EXT:
            if (!schema_does_not_exist_skipping(castNode(List, object), &msg, &name))
            {
				msg = gettext_noop("statistics object \"%s\" does not exist, %s");
                name = NameListToString(castNode(List, object));
            }
            break;
        case OBJECT_TSPARSER:
            if (!schema_does_not_exist_skipping(castNode(List, object), &msg, &name))
            {
				msg = gettext_noop("text search parser \"%s\" does not exist, %s");
                name = NameListToString(castNode(List, object));
            }
            break;
        case OBJECT_TSDICTIONARY:
            if (!schema_does_not_exist_skipping(castNode(List, object), &msg, &name))
            {
				msg = gettext_noop("text search dictionary \"%s\" does not exist, %s");
                name = NameListToString(castNode(List, object));
            }
            break;
        case OBJECT_TSTEMPLATE:
            if (!schema_does_not_exist_skipping(castNode(List, object), &msg, &name))
            {
				msg = gettext_noop("text search template \"%s\" does not exist, %s");
                name = NameListToString(castNode(List, object));
            }
            break;
        case OBJECT_TSCONFIGURATION:
            if (!schema_does_not_exist_skipping(castNode(List, object), &msg, &name))
            {
				msg = gettext_noop("text search configuration \"%s\" does not exist, %s");
                name = NameListToString(castNode(List, object));
            }
            break;
        case OBJECT_EXTENSION:
			msg = gettext_noop("extension \"%s\" does not exist, %s");
            name = strVal((Value *) object);
            break;
        case OBJECT_FUNCTION:
            {
                ObjectWithArgs *owa = castNode(ObjectWithArgs, object);

                if (!schema_does_not_exist_skipping(owa->objname, &msg, &name) &&
                    !type_in_list_does_not_exist_skipping(owa->objargs, &msg, &name))
                {
					msg = gettext_noop("function %s(%s) does not exist, %s");
                    name = NameListToString(owa->objname);
                    args = TypeNameListToString(owa->objargs);
                }
                break;
            }
        case OBJECT_AGGREGATE:
            {
                ObjectWithArgs *owa = castNode(ObjectWithArgs, object);

                if (!schema_does_not_exist_skipping(owa->objname, &msg, &name) &&
                    !type_in_list_does_not_exist_skipping(owa->objargs, &msg, &name))
                {
					msg = gettext_noop("aggregate %s(%s) does not exist, %s");
                    name = NameListToString(owa->objname);
                    args = TypeNameListToString(owa->objargs);
                }
                break;
            }
        case OBJECT_OPERATOR:
            {
                ObjectWithArgs *owa = castNode(ObjectWithArgs, object);

                if (!schema_does_not_exist_skipping(owa->objname, &msg, &name) &&
                    !type_in_list_does_not_exist_skipping(owa->objargs, &msg, &name))
                {
					msg = gettext_noop("operator %s does not exist, %s");
                    name = NameListToString(owa->objname);
                }
                break;
            }
        case OBJECT_LANGUAGE:
			msg = gettext_noop("language \"%s\" does not exist, %s");
            name = strVal((Value *) object);
            break;
        case OBJECT_CAST:
            {
                if (!type_in_list_does_not_exist_skipping(list_make1(linitial(castNode(List, object))), &msg, &name) &&
                    !type_in_list_does_not_exist_skipping(list_make1(lsecond(castNode(List, object))), &msg, &name))
                {
                    /* XXX quote or no quote? */
					msg = gettext_noop("cast from type %s to type %s does not exist, %s");
                    name = TypeNameToString(linitial_node(TypeName, castNode(List, object)));
                    args = TypeNameToString(lsecond_node(TypeName, castNode(List, object)));
                }
            }
            break;
        case OBJECT_TRANSFORM:
            if (!type_in_list_does_not_exist_skipping(list_make1(linitial(castNode(List, object))), &msg, &name))
            {
				msg = gettext_noop("transform for type %s language \"%s\" does not exist, %s");
                name = TypeNameToString(linitial_node(TypeName, castNode(List, object)));
                args = strVal(lsecond(castNode(List, object)));
            }
            break;
        case OBJECT_TRIGGER:
            if (!owningrel_does_not_exist_skipping(castNode(List, object), &msg, &name))
            {
				msg = gettext_noop("trigger \"%s\" for relation \"%s\" does not exist, %s");
                name = strVal(llast(castNode(List, object)));
                args = NameListToString(list_truncate(list_copy(castNode(List, object)),
                                                      list_length(castNode(List, object)) - 1));
            }
            break;
        case OBJECT_POLICY:
            if (!owningrel_does_not_exist_skipping(castNode(List, object), &msg, &name))
            {
				msg = gettext_noop("policy \"%s\" for relation \"%s\" does not exist, %s");
                name = strVal(llast(castNode(List, object)));
                args = NameListToString(list_truncate(list_copy(castNode(List, object)),
                                                      list_length(castNode(List, object)) - 1));
            }
            break;
        case OBJECT_EVENT_TRIGGER:
			msg = gettext_noop("event trigger \"%s\" does not exist, %s");
            name = strVal((Value *) object);
            break;
        case OBJECT_RULE:
            if (!owningrel_does_not_exist_skipping(castNode(List, object), &msg, &name))
            {
				msg = gettext_noop("rule \"%s\" for relation \"%s\" does not exist, %s");
                name = strVal(llast(castNode(List, object)));
                args = NameListToString(list_truncate(list_copy(castNode(List, object)),
                                                      list_length(castNode(List, object)) - 1));
            }
            break;
        case OBJECT_FDW:
			msg = gettext_noop("foreign-data wrapper \"%s\" does not exist, %s");
            name = strVal((Value *) object);
            break;
        case OBJECT_FOREIGN_SERVER:
			msg = gettext_noop("server \"%s\" does not exist, %s");
            name = strVal((Value *) object);
            break;
        case OBJECT_OPCLASS:
            {
                List       *opcname = list_copy_tail(castNode(List, object), 1);

                if (!schema_does_not_exist_skipping(opcname, &msg, &name))
                {
					msg = gettext_noop("operator class \"%s\" does not exist for access method \"%s\", %s");
                    name = NameListToString(opcname);
                    args = strVal(linitial(castNode(List, object)));
                }
            }
            break;
        case OBJECT_OPFAMILY:
            {
                List       *opfname = list_copy_tail(castNode(List, object), 1);

                if (!schema_does_not_exist_skipping(opfname, &msg, &name))
                {
					msg = gettext_noop("operator family \"%s\" does not exist for access method \"%s\", %s");
                    name = NameListToString(opfname);
                    args = strVal(linitial(castNode(List, object)));
                }
            }
            break;
        case OBJECT_PUBLICATION:
			msg = gettext_noop("publication \"%s\" does not exist, %s");
            name = strVal((Value *) object);
            break;
        default:
            elog(ERROR, "unrecognized object type: %d", (int) objtype);
            break;
    }

    if (!args)
		ereport(elevel, (errmsg(msg, name, missmsg)));
    else
		ereport(elevel, (errmsg(msg, name, args, missmsg)));
}
